package ink.mint.modes;

import ink.mint.activation.ActivationType;
import ink.mint.dataset.MnistRead;
import ink.mint.loss.LossType;
import ink.mint.neural.Layer;
import ink.mint.neural.MultiLayerConfiguration;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;

import static ink.mint.activation.ActivationType.*;
import static ink.mint.loss.LossType.*;

public class MultilayeredPerceptronNeuralNetworks {
    /**
     * 训练数据集X
     */
    double[][] X;
    /**
     * 测试数据集XT
     */
    double[][] XT;
    /**
     * 训练数据集标签Y
     */
    double[] Y;
    /**
     * 测试数据集标签YT
     */
    double[] YT;

    /**
     * 数据集主目录
     */
    public String DataSetPath = "";
    /**
     * 模型参数主目录
     */
    public String ParameterPath = "";

    /**
     * 判断软件是否打包安装运行
     */
    boolean isNoPackageOrPackage = false;
    /**
     * 安装包用户路径
     */
    String UserDirPath = "";
    /**
     * 运行函数
     */
    public void run() {
        System.out.println("欢迎使用墨染的模型训练机。 ^_^");
        System.out.println("项目地址: https://gitee.com/Mint-Space/DeepLearning");
        System.out.println("个人网站: https://morn.ink");
        System.out.println();

        String osName = System.getProperty("os.name");
        String osVersion = System.getProperty("os.version");
        String osArch = System.getProperty("os.arch");

        // 打印操作系统信息
        System.out.println("操作系统名称：" + osName);
        System.out.println("操作系统版本：" + osVersion);
        System.out.println("操作系统架构：" + osArch);
        System.out.println();

        if(Objects.equals(osName, "Linux")){
            UserDirPath = System.getProperty("user.dir")+"/../lib";
        }else if(osName.contains("Windows")){
            UserDirPath = System.getProperty("user.dir");
        }
        try {
            System.out.println("请输入数据集路径：（相对路径）默认输入数字 1 是 DataSet/");
            Scanner scanner = new Scanner(System.in);
            String inputDataSetPath = scanner.next();

            if(inputDataSetPath.equals("1")){
                DataSetPath = "DataSet/";
            }else {
                DataSetPath = inputDataSetPath;
            }

            String TRAIN_IMAGES_FILE =  isNoPackageOrPackage ? "src/main/resources/"  + DataSetPath + "train-images/" : UserDirPath + "/app/" +DataSetPath + "train-images/";
            String TRAIN_LABELS_FILE =  isNoPackageOrPackage ? "src/main/resources/"  + DataSetPath + "train-labels/" : UserDirPath + "/app/" +DataSetPath + "train-labels/";
            String TEST_IMAGES_FILE =  isNoPackageOrPackage ? "src/main/resources/"  + DataSetPath + "test-images/" : UserDirPath + "/app/" +DataSetPath + "test-images/";
            String TEST_LABELS_FILE =  isNoPackageOrPackage ? "src/main/resources/"  + DataSetPath + "test-labels/" : UserDirPath + "/app/" +DataSetPath + "test-labels/";

            String ParameterFullFileNamePath = "";

            System.out.println("请设置是否训练还是运行模型：（true or false）前提有模型参数");
            Boolean inputIsTrain = scanner.nextBoolean();
            String inputParameterPath = "";
            if (!inputIsTrain) {
                System.out.println("请输入模型数据路径：（相对路径）默认输入数字 1 是 DataSet/");
                inputParameterPath = scanner.next();
                if(inputParameterPath.equals("1")){
                    ParameterPath = "DataSet/";
                }else {
                    ParameterPath = inputParameterPath;
                }
//                System.out.println(TRAIN_IMAGES_FILE );
                ParameterFullFileNamePath = isNoPackageOrPackage ? "src/main/resources/"  + ParameterPath + "MLPNN.mode" : UserDirPath + "/app/" + ParameterPath + "MLPNN.mode";
//                System.out.println(ParameterFullFileNamePath );
            } else {
                System.out.println("请输入模型数据保存路径：（相对路径）默认输入数字 1 是 DataSet/");
                inputParameterPath = scanner.next();
                if(inputParameterPath.equals("1")){
                    ParameterPath = "DataSet/";
                }else {
                    ParameterPath = inputParameterPath;
                }
//                System.out.println(TRAIN_IMAGES_FILE );
                ParameterFullFileNamePath = isNoPackageOrPackage ? "src/main/resources/"  + ParameterPath + "MLPNN.mode" : UserDirPath + "/app/"  + ParameterPath + "MLPNN.mode";
//                System.out.println(ParameterFullFileNamePath );
            }

            X = MnistRead.getImagesBinarization(readFullFilePath(TRAIN_IMAGES_FILE));
            XT = MnistRead.getImagesBinarization(readFullFilePath(TEST_IMAGES_FILE));

            Y = MnistRead.getLabels(readFullFilePath(TRAIN_LABELS_FILE));
            YT = MnistRead.getLabels(readFullFilePath(TEST_LABELS_FILE));

            if (inputIsTrain) {
                MultiLayerConfiguration multiLayerConfiguration = new MultiLayerConfiguration(X, Y);
                List<Layer> layerList = setLayerParameter();
                int lr = layerList.size();
                for (int i = 0; i < lr; i++) {
                    multiLayerConfiguration.layerConfiguration(layerList.get(i));
                }
                multiLayerConfiguration.saveParameter(ParameterFullFileNamePath);

                LossType lossType = setLossType();
                multiLayerConfiguration.lossConfiguration(lossType);
                System.out.println("请输入学习率LearningRate：");
                double inputLearningRate = scanner.nextDouble();
                multiLayerConfiguration.learningRateConfiguration(inputLearningRate);
                multiLayerConfiguration.train();
                Thread.sleep(10000);
            } else {
                MultiLayerConfiguration mlcRUN = new MultiLayerConfiguration(XT, YT);
                mlcRUN.readParameter(ParameterFullFileNamePath);
                mlcRUN.run();
                Thread.sleep(10000);
            }
        } catch (InputMismatchException e) {
            System.out.println("输入类型错误，请输入相对应类型参数。");
            run();
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("输出层神经元个数与所求维度不一致。");
            run();
        } catch (RuntimeException e) {
            String runtimeExceptionMessage = e.getMessage();
            String[] strs = runtimeExceptionMessage.split(": ");
            if (strs[0].equals("java.io.FileNotFoundException")) {
                System.out.println("输入路径地址错误请输入正确路径，并且有数据集和模型参数。");
                System.out.println(strs[1]);
            }
//            e.printStackTrace();

            System.out.println("输入路径地址错误请输入正确路径，并且有数据集和模型参数。");
            run();
        } catch (FileNotFoundException e) {
            String fileNotFoundMessage = e.getMessage();
            System.out.println("输入路径地址错误请输入正确路径，并且有数据集和模型参数。");
            System.out.println(fileNotFoundMessage);
            run();
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }


    }

    /**
     * 设置网络层参数
     *
     * @return 神经网络网络层
     */
    public List<Layer> setLayerParameter() {
        boolean isDown = true;
        List<Layer> layerList = new ArrayList<Layer>();
        Layer layer;
        Scanner scanner = new Scanner(System.in);
        int i = 1;
        while (isDown) {
            System.out.println("请输入第 " + i + " 层网络神经元参数：（个）[输入值小于0 退出]");
            int inputNeuralNumber = scanner.nextInt();
            if (i == 1 & inputNeuralNumber <= 0) {
                layerList = setLayerParameter();
            }
            if (inputNeuralNumber <= 0) {
                System.out.println("网络参数配置完成");
                return layerList;
            }

            System.out.println("请输入第 " + i + " 层网络神经元激活类型：（1:Sigmoid, 2:RELU,3:Softmax （仅能作为最后一层激活函数使用。）,4:Tanh）输入数字");
            int inputNeuralActivateType = scanner.nextInt();
            if (inputNeuralActivateType > 4) {
                System.out.println("激活函数类别输入错误，重新开始输入。");
                layerList = setLayerParameter();
            }
            ActivationType activationType = getActivtionType(inputNeuralActivateType);
            if (inputNeuralActivateType < 5 && inputNeuralActivateType > 0) {
                layer = new Layer(inputNeuralNumber, activationType);
                layerList.add(layer);
                i++;
            } else {
                isDown = false;
            }
        }
        return layerList;
    }

    /**
     * 设置损失函数类型
     *
     * @return 损失函数类型
     */
    public LossType setLossType() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入输出层的损失函数类型：");
        System.out.println("  1、MeanSquareErrorLossFunction");
        System.out.println("  2、L2LossFunction");
        System.out.println("  3、L1LossFunction");
        System.out.println("  4、SmoothL1LossFunction");
        System.out.println("  5、HuBerLossFunction  超参数delta");
        System.out.println("  6、KullbackLeiblerDivergenceFunction");
        System.out.println("  7、CrossEntropyLossFunction");
        System.out.println("  8、SoftmaxLossFunction");
        System.out.println("  9、FocalLossFunction 超参数alpha和超参数beta");
        int inputLossType = scanner.nextInt();
        LossType lossType = null;
        if (inputLossType < 10 && inputLossType > 0) {
            lossType = getLossType(inputLossType);
        } else {
            System.out.println("输入错误请,重新输入0~9的数。");
            lossType = setLossType();
        }
        return lossType;
    }

    /**
     * 获取激活函数类型
     *
     * @param activtionType 激活函数类型  数字
     * @return 激活函数类型
     */
    private ActivationType getActivtionType(int activtionType) {
        switch (activtionType) {
            case 1:
                return Sigmoid;
            case 2:
                return RELU;
            case 3:
                return Softmax;
            case 4:
                return Tanh;
            default:
                return null;
        }
    }

    /**
     * 获取损失函数类型
     *
     * @param lossType 损失函数类型  数字
     * @return 损失函数类型
     */
    private LossType getLossType(int lossType) {
        switch (lossType) {
            case 1:
                return MeanSquareErrorLossFunction;
            case 2:
                return L2LossFunction;
            case 3:
                return L1LossFunction;
            case 4:
                return SmoothL1LossFunction;
            case 5:
                return HuBerLossFunction;
            case 6:
                return KullbackLeiblerDivergenceFunction;
            case 7:
                return CrossEntropyLossFunction;
            case 8:
                return SoftmaxLossFunction;
            case 9:
                return FocalLossFunction;
            default:
                return null;
        }
    }

    /**
     * 读取全文件路径
     *
     * @param filepath 部分路径（相对路径）
     * @return 全文件路径
     * @throws FileNotFoundException 文件未找到异常
     * @throws IOException           数据流异常
     */
    public String readFullFilePath(String filepath) throws FileNotFoundException, IOException {
        File file = null;
        File readfile = null;
        try {
            file = new File(filepath);
            if (!file.isDirectory()) {
                file.getPath();
                file.getAbsolutePath();
                file.getName();
            } else if (file.isDirectory()) {
                String[] filelist = file.list();
                for (int i = 0; i < filelist.length; i++) {
                    readfile = new File(filepath + "/" + filelist[i]);
                    if (!readfile.isDirectory()) {
                        readfile.getPath();
                        readfile.getAbsolutePath();
                        readfile.getName();
                    } else if (readfile.isDirectory()) {
                        readFullFilePath(filepath + "/" + filelist[i]);
                    }
                }
            }
        } catch (FileNotFoundException e) {
            System.out.println("readfile()   Exception:" + e.getMessage());
        }
        return readfile.getPath();
    }
}
